iT邦幫忙

2022 iThome 鐵人賽

DAY 12
1
DevOps

那些關於 docker 你知道與不知道的事系列 第 12

Day 12: 來用 clone 建立新的 PID namespace

  • 分享至 

  • xImage
  •  

昨天我們用 unshare 建立了新的 namespace,今天我們換個方式,改用另外一個可以用來建立 namespace 的 API clone,我在 github 上找到了一個範例(連結),並且基於這個範例加上了一點資訊:

#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];

char* const container_args[] = {
  "/bin/bash",
  NULL
};

int container_main(void* arg)
{
  printf("Container - inside the container!\n");
  printf("Container - child id: %d\n", getpid());
  printf("Container - parent id: %d\n", getppid());
  execv(container_args[0], container_args);
  printf("Something's wrong!\n");
  return 1;
}

int main()
{
  printf("Parent - start a container!\n");

  int container_pid = clone(container_main, container_stack + STACK_SIZE, CLONE_NEWPID | SIGCHLD, NULL);

  printf("Parent - parent id: %d\n", getpid());
  printf("Parent - child id: %d\n", container_pid);
  waitpid(container_pid, NULL, 0);
  printf("Parent - container stopped!\n");
  return 0;
}

在執行前,我們先來看一下 clone 這個 API,其相關的 man 文件在這裏,有興趣的朋友可以點開來參考一下。clone 函數結構如下:

int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
                 /* pid_t *parent_tid, void *tls, pid_t *child_tid */ );

第一個參數 fn 是一個函數指針,第二個參數 child_stack 則是給 child process 使用的 stack 空間。第三個參數是我們的重點了,我們這邊設定了 CLONE_NEWPID | SIGCHLD,這裡的 CLONE_NEWPID 就是用來告訴 clone,要在一個新的 PID namespace 中建立這個新的 process。除了 CLONE_NEWPID 外,還可以設定的 flag 有 CLONE_NEWCGROUP, CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWUSER, CLONE_NEWUTS 等,跟我們在 Day 10 列出來的 namespace 種類是不是很一致呢。

現在讓我們來編譯並且執行看看:

ubuntu@ip-xxx:~/pid-ns-test$ gcc -o ns ns.c

ubuntu@ip-xxx:~/pid-ns-test$ sudo ./ns
Parent - start a container!
Parent - parent id: 48236
Parent - child id: 48237
Container - inside the container!
Container - child id: 1
Container - parent id: 0

root@ip-xxx:/home/ubuntu/pid-ns-test#  exit
exit
Parent - container stopped!
ubuntu@ip-xxx:~/pid-ns-test$

這邊可以看到,當我們執行了 ns 後,我們會先印出當前這個 process 的 PID 為 48236,利用 clone 這個 API 去建立了一個新的 process,為當前 process 的 child process(當前 process 則為這個新 process 的 parent process),其 PID 是 48237,但在這個新的 process 中,去印出 parent PID 時會是 0,而印自己的 PID 時會是 1,是不是覺得很熟悉呢?

這裡我們可以知道,這是因為新的這個 process 是被執行在一個新的 PID namespace 中,所以他的 PID 會從 1 開始。接著畫面會停在顯示為 root 使用者的輸入命令列,這時候如果輸入 exit,就會回到以 ubuntu 為使用者的命令列中。


這裡我們來做另外一個實驗,把上面那段程式的 clone 中的 CLONE_NEWPID 拿掉,看看這樣會發生什麼事:

ubuntu@ip-xxx:~/pid-ns-test$ sudo ./no-ns
Parent - start a container!
Parent - parent id: 49109
Parent - child id: 49110
Container - inside the container!
Container - child id: 49110
Container - parent id: 49109

root@ip-xxx:/home/ubuntu/pid-ns-test# exit
exit
Parent - container stopped!

是不是看到一個很大的差別呢?如果沒有設定 CLONE_NEWPID 的話,被建立的這個 process 會跟當前 process (也就是 parent process) 在同一個 PID namespace 中。


在執行 ns 程式時,我在新的這個 process 中時,執行了 ps -ef,我以為會像用 unshare 做出來的結果一樣,只看到新的這個 PID namespace 中少少幾個 processes,沒想到事情沒有這麼單純,我還是太天真了...大家可以動手試試看,當執行 ps -ef 的時候,會出現一大堆 processes,就跟在 Host 中看到的沒兩樣!當下是覺得有點被顛覆了,難到我過去的理解都不正確了嗎?後來才發現,我在用 unshare 來建立新的 PID namespace 時,有加上一個參數 --mount-proc,看一下 man page 中對這個參數的解釋:

--mount-proc[=mountpoint]
Just before running the program, mount the proc filesystem at mountpoint (default is /proc). This is useful when creating a new PID namespace. It also implies creating a new mount namespace since the /proc mount would otherwise mess up existing programs on the system. The new proc filesystem is explicitly mounted as private (with MS_PRIVATE|MS_REC).

他的意思是他會掛載一個 proc 檔案系統給掛載點,還是不太懂什麼意思對吧?(還是只有我不懂!?)立刻手動做個實驗:

ubuntu@ip-xxx:~$ sudo unshare --pid --fork /bin/bash
root@ip-xxx:/home/ubuntu# ps -ef |  wc -l
117

果然,只要拿掉 --mount-proc 這個參數,用 ps -ef 查看,會有 117 列資料,跟用上面提供的程式的結果一樣,會看到所有 Host 上的 processes,欸,這又是為什麼呢?


上一篇
Day 11: 繼續來玩一下 PID namespace
下一篇
Day 13: Mount Namespace 的坑
系列文
那些關於 docker 你知道與不知道的事32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言